package edu.hd4ic.algorithm.realizations;

import edu.hd4ic.algorithm.base.AbstractHDBasedAlgorithm;
import edu.hd4ic.algorithm.base.HDValue;
import edu.hd4ic.edu.hd4ic.data.ProcessingImageDataContainer;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: morfant
 * Date: 20.01.13
 * Time: 18:22
 * To change this template use File | Settings | File Templates.
 */
public class CornerWeightHausdorffDistance extends AbstractHDBasedAlgorithm {

    private int cornerImportanceRadius;
    private double cornerImportanceValue;
    private List<int[]> corners;

    public CornerWeightHausdorffDistance(int cornerImportanceRadius, int cornerImportanceValue) {
        this.cornerImportanceRadius = cornerImportanceRadius;
        this.cornerImportanceValue = (double) cornerImportanceValue;
        this.corners = null;
    }

    @Override
    public List<HDValue> compareImage(ProcessingImageDataContainer firstImageContainer,
                                      ProcessingImageDataContainer secondImageContainer,
                                      double calculationThreshold) {
        if (firstImageContainer.getCorners() != null) {
            corners = firstImageContainer.getCorners();
        } else {
            if (secondImageContainer.getCorners() != null) {
                corners = secondImageContainer.getCorners();
            } else {
                throw new RuntimeException("Ни одно из изображений не содержит угловых данных!");
            }
        }
        return super.compareImage(firstImageContainer, secondImageContainer, calculationThreshold);
    }

    private double getWeight(int i, int j) {
        int leftXBorder = i - cornerImportanceRadius;
        int rightXBorder = i + cornerImportanceRadius;
        int leftYBorder = j - cornerImportanceRadius;
        int rightYBorder = j + cornerImportanceRadius;
        int x, y;
        for (int[] position : corners) {
            x = position[0];
            y = position[1];
            if (((x >= leftXBorder)
                    && (x <= rightXBorder))
                    && ((y >= leftYBorder)
                    && (y <= rightYBorder))) {
                return 1d / cornerImportanceValue;
            }
        }
        return 1d;
    }

    @Override
    protected double calculateDirectDistance(int[] firstImageDataBlock, int[] secondImageDataBlock, int width, int height) {

        int cardinality = 0;
        double distanceSum = 0.0;
        int pixelValue;
        int i, j;

        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                pixelValue = firstImageDataBlock[i * width + j];
                if (pixelValue > 0) {
                    distanceSum += getWeight(i, j) * calculateDistanceFromPixelToSet(i, j, secondImageDataBlock, width, height);
                    cardinality++;
                }
            }
        }

        return distanceSum / ((double) cardinality);
    }

    private double calculateDistanceFromPixelToSet(int x, int y, int[] secondImageDataBlock, int width, int height) {
        double distance = 0.0;
        double minDistance = Double.MAX_VALUE;

        int searchIndent = 0;
        int cochleaLenght;
        int pixelValue;

        int i, j, destinationX, destinationY;

        boolean isFound = false;

        while (!isFound) {
            cochleaLenght = 2 * searchIndent + 1;
            for (i = 0; i < cochleaLenght; i++) {

                if ((i == 0) || (i == cochleaLenght - 1)) {//Первая и последняя строки. Интересуют все элементы строки.

                    for (j = 0; j < cochleaLenght; j++) {

                        destinationX = x - searchIndent + i;
                        destinationY = y - searchIndent + j;

                        if (checkThatPositionIsWithinImage(destinationX, destinationY, width, height)) {

                            pixelValue = secondImageDataBlock[destinationX * width + destinationY];

                            if (pixelValue > 0) {

                                isFound = true;
                                distance = calculateEuclideanSpace(x, y, destinationX, destinationY);
                                if (distance < minDistance) {
                                    minDistance = distance;
                                }

                            }
                        }

                    }
                } else {//Промежуточные строки - когда нас интересуют только первый и последний элемент строки.
                    destinationX = x - searchIndent + i;
                    destinationY = y - searchIndent;

                    if (checkThatPositionIsWithinImage(destinationX, destinationY, width, height)) {

                        pixelValue = secondImageDataBlock[destinationX * width + destinationY];

                        if (pixelValue > 0) {

                            isFound = true;
                            distance = calculateEuclideanSpace(x, y, destinationX, destinationY);
                            if (distance < minDistance) {
                                minDistance = distance;
                            }

                        }
                    }


                    destinationX = x - searchIndent + i;
                    destinationY = y + searchIndent;

                    if (checkThatPositionIsWithinImage(destinationX, destinationY, width, height)) {

                        pixelValue = secondImageDataBlock[destinationX * width + destinationY];

                        if (pixelValue > 0) {

                            isFound = true;
                            distance = calculateEuclideanSpace(x, y, destinationX, destinationY);
                            if (distance < minDistance) {
                                minDistance = distance;
                            }

                        }
                    }
                }
            }
            if (!isFound) {
                searchIndent++;
                if (!checkThatWorkIsDoingWithinImage(x, y, searchIndent, width, height)) {
//                    throw new RuntimeException("Полный выход за пределы изображения - одно из изображений пусто.");
                    return Double.MAX_VALUE;
                }
            }
        }

        return minDistance;
    }

    private boolean checkThatWorkIsDoingWithinImage(int x, int y, int indent, int widht, int height) {
        return (x - indent >= 0)//Вверх изображения
                || (y - indent >= 0)//Левая часть изображения
                || (x + indent < widht)//Низ изображения
                || (y + indent < height);//Правая часть изображения
    }

    private boolean checkThatPositionIsWithinImage(int x, int y, int widht, int height) {
        return (x >= 0)
                && (y >= 0)
                && (x < widht)
                && (y < height);
    }

    private double calculateEuclideanSpace(int beginX, int beginY, int endX, int endY) {
        return Math.sqrt(Math.pow(beginX - endX, 2) + Math.pow(beginY - endY, 2));
    }
}
